package ch.uzh.ifi.seal.ssqa.ui.views;

import java.awt.BasicStroke;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;

import org.eclipse.emf.ecore.EObject;

import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.animate.ColorAnimator;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.StrokeAction;
import prefuse.action.layout.Layout;
import prefuse.action.layout.graph.SquarifiedTreeMapLayout;
import prefuse.controls.ControlAdapter;
import prefuse.data.Schema;
import prefuse.data.Tree;
import prefuse.data.expression.Predicate;
import prefuse.data.expression.parser.ExpressionParser;
import prefuse.data.query.SearchQueryBinding;
import prefuse.render.AbstractShapeRenderer;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.FontLib;
import prefuse.util.PrefuseLib;
import prefuse.util.UpdateListener;
import prefuse.visual.DecoratorItem;
import prefuse.visual.NodeItem;
import prefuse.visual.VisualItem;
import prefuse.visual.VisualTree;
import prefuse.visual.expression.InGroupPredicate;
import prefuse.visual.sort.TreeDepthItemSorter;
import ch.uzh.ifi.seal.ssqa.model.CClass;
import ch.uzh.ifi.seal.ssqa.model.Module;
import ch.uzh.ifi.seal.ssqa.model.Source;

public class TreeMapPrefuseComponent extends Display{
	    // create data description of labels, setting colors, fonts ahead of time
	    private static final Schema LABEL_SCHEMA = PrefuseLib.getVisualItemSchema();
	    static {
	        LABEL_SCHEMA.setDefault(VisualItem.INTERACTIVE, false);
	        LABEL_SCHEMA.setDefault(VisualItem.TEXTCOLOR, ColorLib.gray(200));
	        LABEL_SCHEMA.setDefault(VisualItem.FONT, FontLib.getFont("Tahoma",16));
	    }
	    
	    private static final String tree = "tree";
	    private static final String treeNodes = "tree.nodes";
	    private static final String treeEdges = "tree.edges";
	    private static final String labels = "labels";

	    private SearchQueryBinding searchQ;
	    
	    public TreeMapPrefuseComponent(Tree t, String label) {
	        super(new Visualization());
	        
	        // add the tree to the visualization
	        VisualTree vt = m_vis.addTree(tree, t);
	        m_vis.setVisible(treeEdges, null, false);
	        
	        // add labels to the visualization
	        // first create a filter to show labels only at top-level nodes
	        Predicate labelP = (Predicate)ExpressionParser.parse("treedepth()=1");
	        // now create the labels as decorators of the nodes
	        m_vis.addDecorators(labels, treeNodes, labelP, LABEL_SCHEMA);
	        
	        // set up the renderers - one for nodes and one for labels
	        DefaultRendererFactory rf = new DefaultRendererFactory();
	        rf.add(new InGroupPredicate(treeNodes), new NodeRenderer());
	        rf.add(new InGroupPredicate(labels), new LabelRenderer(label));
	        m_vis.setRendererFactory(rf);
	                       
	        // border colors
	        final ColorAction borderColor = new BorderColorAction(treeNodes);
	        final ColorAction fillColor = new FillColorAction(treeNodes);
	        
	        // color settings
	        ActionList colors = new ActionList();
	        colors.add(fillColor);
	        colors.add(borderColor);
	        
	        m_vis.putAction("colors", colors);
	        
	        m_vis.putAction("stroke", new StrokeStyleAction(treeNodes));
	        
	        // animate paint change
	        ActionList animatePaint = new ActionList(400);
	        animatePaint.add(new ColorAnimator(treeNodes));
	        animatePaint.add(new RepaintAction());
	        m_vis.putAction("animatePaint", animatePaint);
	        
	        // create the single filtering and layout action list
	        ActionList layout = new ActionList();
	        layout.add(new SquarifiedTreeMapLayout(tree, 10));
	        layout.add(new LabelLayout(labels));
	        layout.add(colors);
	        layout.add(new RepaintAction());
	        m_vis.putAction("layout", layout);
	        
	        // initialize our display
	        setSize(550,500);
	        setItemSorter(new TreeDepthItemSorter(true));
	        addControlListener(new ControlAdapter() {
	            public void itemEntered(VisualItem item, MouseEvent e) {
	                item.setStrokeColor(borderColor.getColor(item));
	                item.getVisualization().repaint();
	            }
	            public void itemExited(VisualItem item, MouseEvent e) {
	                item.setStrokeColor(item.getEndStrokeColor());
	                item.getVisualization().repaint();
	            }           
	        });
	        
	        searchQ = new SearchQueryBinding(vt.getNodeTable(), label);
	        m_vis.addFocusGroup(Visualization.SEARCH_ITEMS, searchQ.getSearchSet());
	        searchQ.getPredicate().addExpressionListener(new UpdateListener() {
	            public void update(Object src) {
	                m_vis.cancel("animatePaint");
	                m_vis.run("colors");
	                m_vis.run("animatePaint");
	            }
	        });
	        
	        // perform layout
	        m_vis.run("layout");
	    }
	    
	    public SearchQueryBinding getSearchQuery() {
	        return searchQ;
	    }
	    
	    /**
	     * Set the stroke color for drawing treemap node outlines. A graded
	     * grayscale ramp is used, with higer nodes in the tree drawn in
	     * lighter shades of gray.
	     */
	    public static class BorderColorAction extends ColorAction {
	        public BorderColorAction(String group) {
	            super(group, VisualItem.STROKECOLOR);
	        }
	        
	        public int getColor(VisualItem item) {
	            NodeItem nitem = (NodeItem)item;
	            if ( nitem.isHover() ){
	            	return ColorLib.rgb(99,130,191);
	            }
	            return ColorLib.rgb(0, 0, 0);
	        }
	    }
	    
	    
	    public static class StrokeStyleAction extends StrokeAction {
	    	public StrokeStyleAction(String group){
	    		super(group);
	    	}
	    	
	    	public BasicStroke getStroke(VisualItem item){
	    		return new BasicStroke(10);
	    	}
	    }
	    
	    
	    /**
	     * Set fill colors for treemap nodes. Search items are colored
	     * in pink, while normal nodes are shaded according to their
	     * depth in the tree.
	     */
	    public static class FillColorAction extends ColorAction {
	        public FillColorAction(String group) {
	            super(group, VisualItem.FILLCOLOR);
	        }
	        
	        public int getColor(VisualItem item) {
	            if ( item instanceof NodeItem ) {
	                NodeItem nitem = (NodeItem)item;
	                EObject object = (EObject) item.get("element");
	                
	                if ( m_vis.isInGroup(item, Visualization.SEARCH_ITEMS) ){
                        return ColorLib.rgb(255,255,130);
	            	}	
	                
	                if(object instanceof Module){
	                	return ColorLib.rgb(150, 0, 0);
	                } else if (object instanceof Source){
	                	return ColorLib.rgb(200, 50, 50);
	                } else if (object instanceof CClass){
	                	return ColorLib.rgb(255, 160, 160);
	                } else {
	                	return ColorLib.rgb(255,255,255);
	                }
	            } else {
	            	return ColorLib.rgb(255,255,255);
	            }
	        }
	    } // end of inner class TreeMapColorAction
	    
	    /**
	     * Set label positions. Labels are assumed to be DecoratorItem instances,
	     * decorating their respective nodes. The layout simply gets the bounds
	     * of the decorated node and assigns the label coordinates to the center
	     * of those bounds.
	     */
	    public static class LabelLayout extends Layout {
	        public LabelLayout(String group) {
	            super(group);
	        }
	        public void run(double frac) {
	            Iterator iter = m_vis.items(m_group);
	            while ( iter.hasNext() ) {
	                DecoratorItem item = (DecoratorItem)iter.next();
	                VisualItem node = item.getDecoratedItem();
	                Rectangle2D bounds = node.getBounds();
	                setX(item, null, bounds.getCenterX());
	                setY(item, null, bounds.getCenterY());
	            }
	        }
	    } // end of inner class LabelLayout
	    
	    /**
	     * A renderer for treemap nodes. Draws simple rectangles, but defers
	     * the bounds management to the layout.
	     */
	    public static class NodeRenderer extends AbstractShapeRenderer {
	        private Rectangle2D m_bounds = new Rectangle2D.Double();
	        
	        public NodeRenderer() {
	            m_manageBounds = false;
	        }
	        protected Shape getRawShape(VisualItem item) {
	            m_bounds.setRect(item.getBounds());
	            return m_bounds;
	        }
	    } // end of inner class NodeRenderer
}
